﻿// Copyright 2010 Maxime Serrano <lastchronicler@gmail.com>

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Text;

namespace ForgeCraft {
    public sealed class Player : Entity {
        public static Player Console;
        public Session session;
        //public Group group;

        

        public string name;
        public string nick;
        public string lastCMD;
        public string DisconnectReason = null;
        public PlayerInfo info;
        public short currentItem;
        public DateTime lastWarned = DateTime.UtcNow;

        internal DateTime idleTimer = DateTime.UtcNow;
        public const int ChunkArea = 7;  // Size along X and Z of loaded chunks at any point
        public int ChunkAreaPlayer = 8;  // Size along X and Z of loaded chunks at any point
        public int LoadedChunksMinX = 0;
        public int LoadedChunksMinZ = 0;
        public int VisibleChunksMinX = 0;
        public int VisibleChunksMinZ = 0;

        public bool HasChunks = false;

        public List<Chunk> VisibleChunks;
        public List<Chunk> LoadedChunks;
        public List<Entity> VisibleEntities;
        public List<BlockPosition> VisibleSigns;


        public PlayerInventory Inventory;
        public Window CurrentWindow;
        public InventoryItem WindowHolding;
        public BlockPosition queryBlockPos = new BlockPosition(0,255,0);

        private InventoryItem[] _LastEquipment;
        public int SlotSelected;

        static System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();

        public List<Player> localPlayers = new List<Player>();
        object chunkUpdateLock = new object();
        public bool InstantBreak = false;
        public bool waitingCoords = false;

        #region InGame Settings

        public bool godMode;
        public bool isFrozen;
        public Entity isRiding;

        public World currentWorld;

        public DateTime lastMessage = DateTime.UtcNow;

        #endregion

        public Thread chunkLoaderThread;
        public Thread entityLoaderThread;

        #region Constructors

        // Constructor for other players (Console, IRC, etc.)
        public Player(string playerName) {
            name = playerName;
            nick = name;
            info = new PlayerInfo(playerName, Group.findPerm(Permission.Admin));
            Spawned = false;
        }

        // Constructor for just-joined player
        public Player(string playerName, Session playerSession, World currentWorld, Position playerSpawn) {
            name = playerName;
            nick = name;

            this.currentWorld = Server.worlds.Find(world => world.name == Config.GetString(ConfigKey.World));
            currentWorld.playerList.Add(this);

            lastCMD = "";
            session = playerSession;
            info = PlayerDB.FindPlayerInfo(this);

            pos = info.homePos;

            // In-Game Settings
            godMode = false;
            isFrozen = false;
            isRiding = null;

            VisibleEntities = new List<Entity>();
            VisibleChunks = new List<Chunk>();
            VisibleSigns = new List<BlockPosition>();

            LoadedChunks = new List<Chunk>();

            Inventory = new PlayerInventory(this);
            CurrentWindow = null;
            WindowHolding = new InventoryItem(-1);
            _LastEquipment = new InventoryItem[5];
            for (int i = 0; i < 5; ++i)
            {
                _LastEquipment[i] = new InventoryItem(-1);
            }

            //TODO: Check stability issues when these are started from here...
            //chunkLoaderThread = new Thread(new ThreadStart(chunkThread));
            //chunkLoaderThread.IsBackground = true; //Tried removing this but still no looping
                                                   //they shouldn't terminate until the main thread does anyway
            //chunkLoaderThread.Start();

            /*entityLoaderThread = new Thread(new ThreadStart(this.Update));
            entityLoaderThread.IsBackground = true;
            entityLoaderThread.Start();*/
        }

        #endregion

        #region Chunk Updates

        public void chunkThread()
        {
            while (true)
            {
                UpdateChunks();
                Thread.Sleep(1000);
            }
        }

        // Check if chunks need to be loaded / unloaded
        //TODO: Probably move this to some internal world queue, rather than a per-player thread.
        internal void UpdateChunks()
        {
            lock (chunkUpdateLock)
            {
                // Get neccesary chunk range
                int RequiredMinX, RequiredMinZ;

                RequiredMinX = (int)((int)pos.x / 16) - (ChunkAreaPlayer / 2);
                RequiredMinZ = (int)((int)pos.z / 16) - (ChunkAreaPlayer / 2);

                // Check if new chunks are required
                if (RequiredMinX == LoadedChunksMinX && RequiredMinZ == LoadedChunksMinZ)
                    return;

                // Send Pre-Chunks and Chunks
                for (int Xchunk = RequiredMinX; Xchunk < (RequiredMinX + ChunkAreaPlayer); Xchunk++)
                {
                    for (int Zchunk = RequiredMinZ; Zchunk < (RequiredMinZ + ChunkAreaPlayer); Zchunk++)
                    {

                        bool hasChunk = false;

                        foreach (Chunk c in LoadedChunks)
                        {
                            if (c.x == Xchunk && c.z == Zchunk)
                            {
                                hasChunk = true;
                            }
                        }

                        if (!hasChunk)
                        {
                            Chunk chunkToSend = currentWorld.GetChunk(Xchunk, Zchunk);

                            if (chunkToSend == null)
                            {
                                Logger.Log("Player.UpdateChunks: Error loading or generating chunk! ({0}, {1})", LogType.Error, Xchunk, Zchunk);
                                session.KickNow("ERROR: Failed to load or generate chunk! (" + Xchunk + ", " + Zchunk + ")");
                                return;
                            }
                            chunkToSend.AddPlayer(this);

                            LoadedChunks.Add(chunkToSend);

                            if (!chunkToSend.terrainPopulated)
                                chunkToSend.CalculateLighting();

                            if (!Spawned)
                                chunkToSend.CalculateLighting();

                            // Some attempt at a queue
                            /*Chunk.ChunkToProcess ccp = new Chunk.ChunkToProcess();

                            ccp.c = chunkToSend;
                            ccp.p = this;
                            ccp.toLoad = true;

                            Server.ChunkToLoadQueue.Add(ccp);*/

                            //Logger.Log("Sending: " + Xchunk + " " + Zchunk, LogType.Debug);
                            session.SendNow(PacketWriter.MakePreChunk(Xchunk, Zchunk, true));
                            session.SendNow(PacketWriter.MakeChunk(chunkToSend));
                        }
                    }
                }

                // Unload old chunks (if they have old chunks)
                if (HasChunks)
                {
                    for (int Xchunk = LoadedChunksMinX; Xchunk < (LoadedChunksMinX + ChunkAreaPlayer); Xchunk++)
                    {
                        for (int Zchunk = LoadedChunksMinZ; Zchunk < (LoadedChunksMinZ + ChunkAreaPlayer); Zchunk++)
                        {
                            if ((Xchunk > (RequiredMinX + ChunkAreaPlayer) || Xchunk < (RequiredMinX) || Zchunk > (RequiredMinZ + ChunkAreaPlayer) || Zchunk < (RequiredMinZ)) && (HasChunks))
                            {

                                session.SendNow(PacketWriter.MakePreChunk(Xchunk, Zchunk, false));
                                Chunk chunkToRemove = currentWorld.GetChunk(Xchunk, Zchunk);
                                if (chunkToRemove != null)
                                {
                                    LoadedChunks.Remove(chunkToRemove);
                                    chunkToRemove.RemovePlayer(this);
                                }
                            }
                        }
                    }
                }
                else
                {
                    HasChunks = true;
                    if (!Spawned) Spawned = true;
                    // TODO: Below shouldn't be here, it should be in the login function, but that isn't working.
                    pos = currentWorld.spawn; //spawning seems broken atm, this is a quick fix
                    session.SendNow(PacketWriter.MakePositionAndLook(pos));
                }

                // Set new loaded chunk range
                LoadedChunksMinX = RequiredMinX;
                LoadedChunksMinZ = RequiredMinZ;

                if (ChunkAreaPlayer < info.group.drawLimit && (!info.group.drawLimitLocked))
                {
                    ChunkAreaPlayer++;
                }
            }
        }


        #endregion

        #region Blocks

        public void DestroyBlock(int x, byte y, int z) {
            //TODO: Check player can acutally remove block with right tool.
            //TODO: Stop player from building in nothing. - Fixed? (No, just handled differently)

            //BlockPosition bp = new BlockPosition(x, y, z);

            //InventoryItem i = Inventory.slots[36 + SlotSelected];

            // Check for special types
            ChunkBlock b = currentWorld.GetBlock(x, y, z);
            if (b.type == (byte)Block.Adminium) return;
            if (b.type == (byte)Block.Air) 
            { 
                //Doesn't always seem to send packets, this is a double check.
                //25th - Going to comment this out, see if its fixed now.
                //Send(PacketWriter.MakeBlockChange(b, x, y, z)); 
                return; 
            }


            //Metadata isn't automatically passed (I think)
            //Switch statement decides what is spawned to the player.
            InventoryItem spawnedItem = new InventoryItem(b.type);
            int qty = 1;

            switch ((Block)spawnedItem.Type)
            {
                //TODO: Put this in an enum or make goodies & qty configurable.
                case Block.TNT:
                    new ObjectEntity(x, y, z, new ObjectItem((byte)Object.ActivatedTNT), currentWorld);
                    //currentWorld.ExplodeTNT(x, y, z, 1);
                    spawnedItem.Count = 0;
                    break;

                case Block.Stone:
                    spawnedItem.Type = (short)Block.Cobblestone;
                    break;

                case Block.Grass:
                case Block.Soil:
                    spawnedItem.Type = (short)Block.Dirt;
                    break;

                case Block.Gravel:
                    if (Server.random.Next(0, 10) == 5) { new PickupEntity(x, y, z, new InventoryItem((short)Item.Flint, 1, 1), currentWorld, new Velocity(0,0,0)); }
                    break;

                case Block.Fire:
                    spawnedItem.Count = 0;
                    break;

                case Block.Leaves:
                    if (Server.random.Next(0, 10) == 5) { spawnedItem.Type = (short)Block.Sapling; }
                    else spawnedItem.Count = 0;
                    break;

                case Block.Glass:
                    spawnedItem.Count = 0;
                    break;

                case Block.LapisOre:
                    spawnedItem.Type = (short)Item.Dye;
                    spawnedItem.Damage = (short)Metadata.Dyes.LapisLazuli;
                    spawnedItem.Count = 4;
                    break;

                case Block.LapisBlock:
                    spawnedItem.Type = (short)Item.Dye;
                    spawnedItem.Damage = (short)Metadata.Dyes.LapisLazuli;
                    spawnedItem.Count = 9;
                    break;

                case Block.DoubleStair:
                    spawnedItem.Type = (short)Block.Stair;
                    spawnedItem.Damage = b.metadata;
                    spawnedItem.Count = 2;
                    break;

                case Block.Stair:
                    spawnedItem.Damage = b.metadata;
                    break;

                case Block.Wool:
                    spawnedItem.Damage = b.metadata;
                    break;

                case Block.RedstoneWire:
                    spawnedItem.Type = (short)Item.RedstoneDust;
                    break;

                case Block.DiamondOre:
                    spawnedItem.Type = (short)Item.Diamond;
                    break;

                case Block.Crops:
                    if (b.metadata == 0x7)
                    {
                        new PickupEntity(x, y, z, new InventoryItem((short)Item.Wheat, 1, 0), currentWorld, new Velocity(0, 0, 0));
                        spawnedItem.Type = (short)Item.Seeds;
                        spawnedItem.Count = 2;
                    }
                    else
                    {
                        spawnedItem.Count = 0;
                    }
                    break;

                case Block.Reed:
                case Block.Cactus:
                    ChunkBlock cbc = b;

                    for (byte i = 0; i < 127; i++)
                    {
                        if (y + i > 127) break;
                        cbc = currentWorld.GetBlock(x, (byte)(y + i), z);

                        if (cbc.type == b.type)
                        {
                            currentWorld.ChangeBlock(new ChunkBlock(Block.Air, 0, 0x00), x, (byte)(y + i), z, this, true);
                            spawnedItem.Count++;
                        }
                        else
                        {
                            break;
                        }
                    }
                    if ((Block)b.type == Block.Reed)
                    {
                        spawnedItem.Type = (short)Item.Reed;
                        spawnedItem.Count = 3;
                    }
                    break;

                case Block.BurningFurnace:
                    spawnedItem.Type = (short)Block.Furnace;
                    break;

                case Block.Signpost:
                case Block.WallSign:
                    //TODO: Removing this will delete all teh signs in the chunk!
                    //TODO: Also check the exact coords, maybe a separate function.
                    if (currentWorld.SignList.ContainsKey(new BlockPosition(x, y, z)))
                    {
                        currentWorld.SignList.Remove(new BlockPosition(x, y, z));
                    }

                    spawnedItem.Type = (short)Item.Sign;
                    break;
                case Block.WoodenDoor:
                    spawnedItem.Type = (short)Item.WoodenDoor;
                    break;

                case Block.IronDoor:
                    spawnedItem.Type = (short)Item.IronDoor;
                    break;

                case Block.RedstoneOre:
                case Block.GlowingRedstoneOre:
                    spawnedItem.Type = (short)Item.RedstoneDust;
                    spawnedItem.Count = 4;
                    break;

                case Block.RedstoneTorchOn:
                    spawnedItem.Type = (short)Block.RedstoneTorchOff;
                    break;

                //Just destroy these
                case Block.SnowSurface:
                case Block.Ice:
                case Block.Portal:
                    spawnedItem.Count = 0;
                    break;

                case Block.SnowBlock:
                    spawnedItem.Type = (short)Item.Snowball;
                    spawnedItem.Count = 8;
                    break;

                case Block.Clay:
                    spawnedItem.Type = (short)Item.Clay;
                    spawnedItem.Count = 4;
                    break;

                case Block.CoalOre:
                    spawnedItem.Type = (short)Item.Coal;
                    break;

                case Block.Glowstone:
                    spawnedItem.Type = (short)Item.GlowstoneDust;
                    spawnedItem.Count = 1;
                    break;

                case Block.RepeaterOff:
                case Block.RepeaterOn:
                    spawnedItem.Type = (short)Item.RedstoneRepeater;
                    break;
            }

            //Are there any containers at the coords?
            currentWorld.WindowList.TryGetValue(new BlockPosition(x, y, z), out CurrentWindow);

            if (CurrentWindow != null) //If so, erase this from the masterlist.
            {
                if (CurrentWindow.ownerName != this.name && !CurrentWindow.isPublic)
                {
                    Message(Color.Red + "[!] " + Color.Sys + "This block is owned by " + CurrentWindow.ownerName);
                    CurrentWindow = null;
                    return;
                }

                CurrentWindow.Remove(currentWorld);
                currentWorld.WindowList.Remove(new BlockPosition(x, y, z));
                
                CurrentWindow = null;
            }

            //Spawn Goodies
            if (spawnedItem.Count > 0)
            {
                for (int count = 0; count < qty; count++)
                {
                    new PickupEntity(x, y, z, spawnedItem, currentWorld, new Velocity(0, 0, 0));
                }
            }

            //Check if item should be damaged.
            Inventory.DamageItemSlot((short)(36 + SlotSelected));

            //This probably isn't needed, as we're not doing anything the client isnt expecting.
            //Inventory.SetSlot((short)(36 + SlotSelected), i);

            // Set block
            currentWorld.ChangeBlock(new ChunkBlock(Block.Air, 0, 0x00), x, y, z, this);
        }

        //Used when the player places an item with ID over 255
        public void PlaceItem(int x, byte y, int z, byte dir, InventoryItem placedItem)
        {
            switch (dir)
            {
                case 0: --y; break;
                case 1: ++y; break;
                case 2: --z; break;
                case 3: ++z; break;
                case 4: --x; break;
                case 5: ++x; break;
                default: return;
            }

            ChunkBlock existingBlock = currentWorld.GetBlock(x, y, z);
            ChunkBlock underBlock = currentWorld.GetBlock(x, (byte)(y - 1), z);

            //Message("Direction is: " + dir + " B:" + existingBlock.type + " U:" + underBlock.type);

            byte cpoint = GetCompassDirection(4);

            switch ((Item)placedItem.Type)
            {
                case Item.DiamondHoe:
                case Item.GoldHoe:
                case Item.IronHoe:
                case Item.StoneHoe:
                case Item.WoodenHoe:
                    if ((Block)underBlock.type == Block.Dirt || (Block)underBlock.type == Block.Grass)
                    {
                        currentWorld.ChangeBlock(new ChunkBlock(Block.Soil, 0, 0x00), x, (byte)(y - 1), z, this);
                    }
                    break;
                case Item.Sign:
                    if (dir < 2) // Floor Sign
                    {
                        byte metadata = 0;
                        cpoint = GetCompassDirection(8);

                        switch (cpoint)
                        {
                            case 1:
                                metadata = 0xC; //(byte)Metadata.SignPost.North;
                                break;
                            case 2:
                                metadata = 0xE; //(byte)Metadata.SignPost.Northeast;
                                break;
                            case 3:
                                metadata = 0x0; //(byte)Metadata.SignPost.East;
                                break;
                            case 4:
                                metadata = 0x2; //(byte)Metadata.SignPost.Southeast;
                                break;
                            default:
                                metadata = 0x4; //(byte)Metadata.SignPost.South;
                                break;
                            case 6:
                                metadata = 0x6; //(byte)Metadata.SignPost.Southwest;
                                break;
                            case 7:
                                metadata = 0x8; //(byte)Metadata.SignPost.West;
                                break;
                            case 8:
                                metadata = 0xA; //(byte)Metadata.SignPost.Northwest;
                                break;
                        }

                        currentWorld.ChangeBlock(new ChunkBlock(Block.Signpost, metadata, 0x00), x, y, z, this);
                    }
                    else // Wall Sign
                    {
                        byte metadata = 0;

                        switch (dir)
                        {
                            case 2: metadata = (byte)Metadata.SignWall.East; 
                                break;
                            case 3: metadata = (byte)Metadata.SignWall.West; 
                                break;
                            case 4: metadata = (byte)Metadata.SignWall.North; 
                                break;
                            case 5: metadata = (byte)Metadata.SignWall.South; 
                                break;
                        }

                        currentWorld.ChangeBlock(new ChunkBlock(Block.WallSign, metadata, 0x00), x, y, z, this);
                    }
                    Inventory.PopItemSlot((short)(36 + SlotSelected));
                    break;
                case Item.Seeds:
                    if ((Block)underBlock.type == Block.Soil)
                        currentWorld.ChangeBlock(new ChunkBlock(Block.Crops, 0, 0x00), x, y, z, this);
                    Inventory.PopItemSlot((short)(36 + SlotSelected));
                    break;
                case Item.RedstoneDust:
                    currentWorld.ChangeBlock(new ChunkBlock(Block.RedstoneWire, 0, 0x00), x, y, z, this);
                    Inventory.PopItemSlot((short)(36 + SlotSelected));
                    break;
                case Item.Minecart:
                    new ObjectEntity(x, y, z, new ObjectItem((byte)Object.Minecart), currentWorld);
                    Inventory.PopItem(Inventory.slots[36 + SlotSelected]);
                    break;
                case Item.Boat:
                    new ObjectEntity(x, y, z, new ObjectItem((byte)Object.Boat), currentWorld);
                    Inventory.PopItem(Inventory.slots[36 + SlotSelected]);
                    break;
                case Item.StorageMinecart:
                    new ObjectEntity(x, y, z, new ObjectItem((byte)Object.StorageCart), currentWorld);
                    Inventory.PopItem(Inventory.slots[36 + SlotSelected]);
                    break;
                case Item.PoweredMinecart:
                    new ObjectEntity(x, y, z, new ObjectItem((byte)Object.PoweredCart), currentWorld);
                    Inventory.PopItem(Inventory.slots[36 + SlotSelected]);
                    break;
                    
            }

            Inventory.DamageItemSlot((short)(36 + SlotSelected));
        }

        //Typically called from the packet, goes to PlaceItem if not a block.
        public void PlaceBlock(int x, byte y, int z, byte dir, InventoryItem placedItem)
        {
            ChunkBlock existingBlock = currentWorld.GetBlock(x, y, z);
            
            if (Blocks.WindowBlocks.Contains((Block)existingBlock.type))
            {

                if (!currentWorld.WindowList.ContainsKey(new BlockPosition(x, y, z)))
                {
                    switch ((Block)existingBlock.type)
                    {
                        case Block.Chest:
                            CurrentWindow = new ChestWindow(this, new BlockPosition(x, y, z));
                            break;
                        case Block.Workbench:
                            CurrentWindow = new WorkBenchWindow(this, new BlockPosition(x, y, z));
                            break;
                        case Block.Furnace:
                        case Block.BurningFurnace:
                            CurrentWindow = new FurnaceWindow(this, new BlockPosition(x, y, z));
                            break;
                        case Block.Dispenser:
                            CurrentWindow = new DispenserWindow(this, new BlockPosition(x, y, z));
                            break;
                    }
                }
                else
                {
                    currentWorld.WindowList.TryGetValue(new BlockPosition(x, y, z), out CurrentWindow);

                    if (CurrentWindow.ownerName != this.name && !CurrentWindow.isPublic)
                    {
                        Message(Color.Red + "[!] " + Color.Sys + "This block is owned by " + CurrentWindow.ownerName);
                        CurrentWindow = null;
                        return;
                    }
                }
                CurrentWindow.Open(this);
                return;
            }

            // Empty hand, can't go any futher.
            if (placedItem.Type <= 0) return;

            if (placedItem.Type > 255)
            {
                //Message("Used item " + placedItem.Type);
                PlaceItem(x, y, z, dir, placedItem);
                return;
            }

            // Find block coordinate, original coords are the block being built against.
            switch (dir)
            {
                case 0: --y; break;
                case 1: ++y; break;
                case 2: --z; break;
                case 3: ++z; break;
                case 4: --x; break;
                case 5: ++x; break;
                default: return;
            }

            ChunkBlock underBlock = currentWorld.GetBlock(x, (byte)(y - 1), z);

            ChunkBlock placedBlock = new ChunkBlock((byte)placedItem.Type, (byte)placedItem.Damage, 0x00);

            if (Inventory.slots[36 + SlotSelected].Damage > 0)
            {
                placedBlock.metadata = (byte)Inventory.slots[36 + SlotSelected].Damage;
            }

            // Make block
            bool allowPlace = true;
            byte placedLight = 0;
            if (Blocks.LightCarryingBlocks.Contains((Block)placedBlock.type))
                placedBlock.lighting = existingBlock.lighting;
            foreach (BlockInfo info in Blocks.LightEmittingBlocks) {
                if (info.type == (Block)placedBlock.type)
                {
                    placedBlock.lighting = info.info;
                    break;
                }
            }

            byte cpoint = GetCompassDirection(4);
           
            // Block logic
            switch ((Block)placedBlock.type)
            {
                case Block.Furnace:
                case Block.Dispenser:
                    switch (dir) // This is so screwed, as the client has a mind of its own...
                    {
                        case 2: placedBlock.metadata = (byte)Metadata.Furnace.East; break;
                        case 3: placedBlock.metadata = (byte)Metadata.Furnace.West; break;
                        case 4: placedBlock.metadata = (byte)Metadata.Furnace.North; break;
                        case 5: placedBlock.metadata = (byte)Metadata.Furnace.South; break;
                        default:
                            switch (cpoint) // This is so screwed, as the client has a mind of its own...
                            {
                                case 1:
                                    placedBlock.metadata = (byte)Metadata.Furnace.North;
                                    break;
                                case 3:
                                    placedBlock.metadata = (byte)Metadata.Furnace.West;
                                    break;
                                case 5:
                                    placedBlock.metadata = (byte)Metadata.Furnace.South;
                                    break;
                                default:
                                    placedBlock.metadata = (byte)Metadata.Furnace.East;
                                    break;
                            }
                            break;
                    }
                    break;

                case Block.WoodenStairs:
                case Block.CobblestoneStairs:
                        switch (cpoint)
                        {
                            case 1:
                                placedBlock.metadata = (byte)Metadata.Stairs.North;
                                break;
                            case 3:
                                placedBlock.metadata = (byte)Metadata.Stairs.East;
                                break;
                            case 5:
                                placedBlock.metadata = (byte)Metadata.Stairs.South;
                                break;
                            default:
                                placedBlock.metadata = (byte)Metadata.Stairs.West;
                                break;
                        }
                        break;
                case Block.Rails:
                    switch (cpoint)
                    {
                        case 1:
                        case 5:
                            placedBlock.metadata = 0x1; // NorthSouth
                            break;
                        default:
                            placedBlock.metadata = 0x0; // EastWest
                            break;
                    }

                    if ((Block)currentWorld.GetBlock(x + 1, (byte)(y + 1), z).type == Block.Rails)
                    { placedBlock.metadata = (byte)Metadata.Tracks.RiseSouth; break; }
                    if ((Block)currentWorld.GetBlock(x - 1, (byte)(y + 1), z).type == Block.Rails)
                    {    placedBlock.metadata = (byte)Metadata.Tracks.RiseNorth; break; }
                    if ((Block)currentWorld.GetBlock(x, (byte)(y + 1), z + 1).type == Block.Rails)
                    {    placedBlock.metadata = (byte)Metadata.Tracks.RiseWest; break; }
                    if ((Block)currentWorld.GetBlock(x, (byte)(y + 1), z - 1).type == Block.Rails)
                    {   placedBlock.metadata = (byte)Metadata.Tracks.RiseEast; break; }

                    if ((Block)currentWorld.GetBlock(x + 1, y, z).type == Block.Rails)
                        if ((Block)currentWorld.GetBlock(x, y, z - 1).type == Block.Rails)
                            placedBlock.metadata = 0x9; //NorthWest
                    if ((Block)currentWorld.GetBlock(x + 1, y, z).type == Block.Rails)
                        if ((Block)currentWorld.GetBlock(x, y, z + 1).type == Block.Rails)
                            placedBlock.metadata = 0x6; //NorthEast
                    if ((Block)currentWorld.GetBlock(x - 1, y, z).type == Block.Rails)
                        if ((Block)currentWorld.GetBlock(x, y, z + 1).type == Block.Rails)
                            placedBlock.metadata = 0x7; //SouthEast
                    if ((Block)currentWorld.GetBlock(x - 1, y, z).type == Block.Rails)
                        if ((Block)currentWorld.GetBlock(x, y, z - 1).type == Block.Rails)
                            placedBlock.metadata = 0x8; //SouthWest

                    if ((Block)currentWorld.GetBlock(x + 1, (byte)(y + 1), z).type == Block.Rails)
                        if ((Block)currentWorld.GetBlock(x, (byte)(y + 1), z - 1).type == Block.Rails)
                            placedBlock.metadata = 0x9; //NorthWest
                    if ((Block)currentWorld.GetBlock(x + 1, (byte)(y + 1), z).type == Block.Rails)
                        if ((Block)currentWorld.GetBlock(x, (byte)(y + 1), z + 1).type == Block.Rails)
                            placedBlock.metadata = 0x6; //NorthEast
                    if ((Block)currentWorld.GetBlock(x - 1, (byte)(y + 1), z).type == Block.Rails)
                        if ((Block)currentWorld.GetBlock(x, (byte)(y + 1), z + 1).type == Block.Rails)
                            placedBlock.metadata = 0x7; //SouthEast
                    if ((Block)currentWorld.GetBlock(x - 1, (byte)(y + 1), z).type == Block.Rails)
                        if ((Block)currentWorld.GetBlock(x, (byte)(y + 1), z - 1).type == Block.Rails)
                            placedBlock.metadata = 0x8; //SouthWest
                    break;
                case Block.Torch:
                    if (dir == 0) allowPlace = false;
                    else if (dir == 1) placedBlock.metadata = (byte)Metadata.Torch.Standing;
                    else if (dir == 2) placedBlock.metadata = (byte)Metadata.Torch.West;
                    else if (dir == 3) placedBlock.metadata = (byte)Metadata.Torch.East;
                    else if (dir == 4) placedBlock.metadata = (byte)Metadata.Torch.North;
                    else if (dir == 5) placedBlock.metadata = (byte)Metadata.Torch.South;
                    placedBlock.lighting = placedLight;
                    break;
                case Block.Stair:
                    if ((Block)underBlock.type == Block.Stair)
                    {
                        y = (byte)(y - 1);
                        placedBlock.type = (byte)Block.DoubleStair;
                    }
                    else if ((Block)existingBlock.type == Block.Stair)
                    {
                        placedBlock.type = (byte)Block.DoubleStair;
                    }
                    break;
                case Block.Cactus:
                    allowPlace = false;

                    if ((Block)underBlock.type == Block.Sand || (Block)underBlock.type == Block.Cactus)
                    {
                        allowPlace = true;
                    }
                    break;
                case Block.Crops:
                    allowPlace = false;

                    if ((Block)underBlock.type == Block.Soil && underBlock.metadata > 1)
                    {
                        allowPlace = true;
                    }
                    break;
            }

            // If not allowed to place, send real block to player and stop
            if (!allowPlace)
            {
                //Send(PacketWriter.MakeBlockChange(existingBlock, x, y, z));
                return;
            }

            //Decrement inventory counts;
            Inventory.slots[36 + SlotSelected] = Inventory.PopItem(Inventory.slots[36 + SlotSelected]);

            // Set and update block
            currentWorld.ChangeBlock(placedBlock, x, y, z, this);
        }

        #endregion

        #region Template Messages

        internal void NoPlayerMessage(string name) {
            Message(Color.Sys + "No players found matching \"" + name + "\"");
        }


        internal void ManyPlayersMessage(string name) {
            Message(Color.Sys + "More than one player found matching \"" + name + "\"");
        }


        internal void NoAccessMessage(params Permission[] permissions) {
            Message(Color.Red + "You do not have access to this command.");
            if (permissions.Length == 1) {
                Message(Color.Red + "You need " + permissions[0].ToString() + " permission.");
            } else {
                Message(Color.Red + "You need the following permissions:");
                foreach (Permission permission in permissions) {
                    Message(Color.Red + permission.ToString());
                }
            }
        }

        #endregion

        #region Chat / Messages

        // Queues a system message
        public void Message(string message) {
            Message(">", message);
        }

        // Queues a wrapped system message with a custom color
        public void Message(string prefix, string message) {
            if (session == null) {
                Logger.LogConsole(message);
            } else {
                foreach (Packet p in PacketWriter.MakeWrappedMessage(prefix, Color.Sys + message, false)) {
                    session.Send(p);
                }
            }
        }

        // Parses message incoming from the player
        public void ParseMessage(string message, bool fromConsole) {
            try
            {
                //AFK Stuff
                /*if (text != "/afk")
                {
                    if (Server.afkset.Contains(this.name))
                    {
                        Server.afkset.Remove(this.name);
                        Player.GlobalMessage("-" + this.color + this.name + Server.DefaultColor + "- is no longer AFK");
                        IRCBot.Say(this.name + " is no longer AFK");
                    }
                }*/

                //Command Handler
                if (message[0] == '/' || message[0] == '!')
                {
                    message = message.Remove(0, 1);

                    int pos = message.IndexOf(' ');
                    if (pos == -1)
                    {
                        HandleCommand(message.ToLower(), "");
                        return;
                    }
                    string cmd = message.Substring(0, pos).ToLower();
                    string msg = message.Substring(pos + 1);
                    HandleCommand(cmd, msg);
                    return;
                }

                //Chatmod & Voice
                //if (Server.chatmod && !this.voice) { this.SendMessage("Chat moderation is on, you cannot speak."); return; }
                //if (muted) { this.SendMessage("You are muted."); return; }  //Muted: Only allow commands

                //Private Messages
                if (message[0] == '@')
                {
                    string newtext = message;
                    if (message[0] == '@') newtext = message.Remove(0, 1).Trim();

                    string otherPlayerName = message.Substring(0, message.IndexOf(' ') - 1);
                    Player otherPlayer = Server.FindPlayer(otherPlayerName);
                    if (otherPlayer != null)
                    {
                        Logger.Log("{0} to {1}: {2}", LogType.PrivateChat, GetLogName(), otherPlayer.GetLogName(), message);
                        otherPlayer.Message(Color.Gray + "from " + name + ": " + message.Substring(message.IndexOf(' ') + 1));
                        Message(Color.Gray + "to " + otherPlayer.name + ": " + message.Substring(message.IndexOf(' ') + 1));
                        return;
                    }
                    else
                    {
                        NoPlayerMessage(otherPlayerName);
                        return;
                    }
                }

                //TODO: Player Class Messages
                /*case MessageType.ClassChat:
                if (!Can(Permission.Chat)) return;
                string className = message.Substring(2, message.IndexOf(' ') - 2);
                PlayerClass playerClass = ClassList.FindClass(className);
                if (playerClass != null) {
                    Logger.Log("{0} to class {1}: {2}", LogType.ClassChat, GetLogName(), playerClass.name, message);
                    Packet classMsg = PacketWriter.MakeMessage(Color.Gray + "[" + playerClass.color + playerClass.name + Color.Gray + "]" + nick + ": " + message.Substring(message.IndexOf(' ') + 1));
                    Server.SendToClass(classMsg, playerClass);
                    if (info.playerClass != playerClass) {
                        Send(classMsg);
                    }
                } else {
                    Message("No class found matching \"" + className + "\"");
                }
                break;*/

                //OP Chat Messages
                /*if (message[0] == '#')
                {
                    string newtext = message;
                    if (message[0] == '#') newtext = message.Remove(0, 1).Trim();

                    GlobalMessageOps("To Ops &f-" + color + name + "&f- " + newtext);
                    if (group.Permission < Server.opchatperm && !Server.devs.Contains(name.ToLower()))
                        SendMessage("To Ops &f-" + color + name + "&f- " + newtext);
                    Server.s.Log("(OPs): " + name + ": " + newtext);
                    IRCBot.Say(name + ": " + newtext, true);
                    return;
                }*/

                //Trololololol
                /*if (this.joker)
                {
                    if (File.Exists("text/joker.txt"))
                    {
                        Server.s.Log("<JOKER>: " + this.name + ": " + message);
                        Player.GlobalMessageOps(Server.DefaultColor + "<&aJ&bO&cK&5E&9R" + Server.DefaultColor + ">: " + this.color + this.name + ":&f " + message);
                        FileInfo jokertxt = new FileInfo("text/joker.txt");
                        StreamReader stRead = jokertxt.OpenText();
                        List<string> lines = new List<string>();
                        Random rnd = new Random();
                        int i = 0;

                        while (!(stRead.Peek() == -1))
                            lines.Add(stRead.ReadLine());

                        i = rnd.Next(lines.Count);

                        stRead.Close();
                        stRead.Dispose();
                        message = lines[i];
                    }
                    else { File.Create("text/joker.txt"); }

                }*/
                info.linesWritten++;
                string displayedName = nick;

                if (Config.GetBool(ConfigKey.ClassColorsInChat) && info.group.color.Length > 0 && info.group.color != Color.White)
                {
                    displayedName = info.group.color + displayedName + Color.White;
                }

                //TODO: Check console doesn't need a displayed name set!

                if (name.ToLower() == "dag10") displayedName = "§4d§ca§eg§a1§b0§f";
                if (name.ToLower() == "fragmer") displayedName = "§4f§cr§ea§ag§bm§9e§5r§f";
                if (name.ToLower() == "listings09") displayedName = "§4L§cI§eS§aT§bI§9N§5G§f";
                if (name.ToLower() == "t101") displayedName = "§4T§c1§e0§a1§f";

                //Correct colors
                /*for (int i = 0; i < 10; i++)
                {
                    message = message.Replace("%" + i, "§" + i);
                    message = message.Replace("§" + i + " §", " §");
                }
                for (char ch = 'a'; ch <= 'f'; ch++)
                {
                    message = message.Replace("%" + ch, "§" + ch);
                    message = message.Replace("§" + ch + " §", " §");
                }*/


                message = message.Replace("$name", name);
                message = message.Replace("$date", DateTime.Now.ToString("yyyy-MM-dd"));
                message = message.Replace("$time", DateTime.Now.ToString("HH:mm:ss"));
                if (!fromConsole) message = message.Replace("$ip", this.session.GetIP().ToString());


                message = message.Replace(":)", "(darksmile)");
                message = message.Replace(":D", "(smile)");
                message = message.Replace("<3", "(heart)");

                byte[] stored = new byte[1];

                stored[0] = (byte)1;
                message = message.Replace("(darksmile)", enc.GetString(stored));
                stored[0] = (byte)2;
                message = message.Replace("(smile)", enc.GetString(stored));
                stored[0] = (byte)3;
                message = message.Replace("(heart)", enc.GetString(stored));
                stored[0] = (byte)4;
                message = message.Replace("(diamond)", enc.GetString(stored));
                stored[0] = (byte)7;
                message = message.Replace("(bullet)", enc.GetString(stored));
                stored[0] = (byte)8;
                message = message.Replace("(hole)", enc.GetString(stored));
                stored[0] = (byte)11;
                message = message.Replace("(male)", enc.GetString(stored));
                stored[0] = (byte)12;
                message = message.Replace("(female)", enc.GetString(stored));
                stored[0] = (byte)15;
                message = message.Replace("(sun)", enc.GetString(stored));
                stored[0] = (byte)16;
                message = message.Replace("(right)", enc.GetString(stored));
                stored[0] = (byte)17;
                message = message.Replace("(left)", enc.GetString(stored));
                stored[0] = (byte)19;
                message = message.Replace("(double)", enc.GetString(stored));
                stored[0] = (byte)22;
                message = message.Replace("(half)", enc.GetString(stored));
                stored[0] = (byte)24;
                message = message.Replace("(uparrow)", enc.GetString(stored));
                stored[0] = (byte)25;
                message = message.Replace("(downarrow)", enc.GetString(stored));
                stored[0] = (byte)26;
                message = message.Replace("(rightarrow)", enc.GetString(stored));
                stored[0] = (byte)30;
                message = message.Replace("(up)", enc.GetString(stored));
                stored[0] = (byte)31;
                message = message.Replace("(down)", enc.GetString(stored));

                Server.SendToAll("<" + displayedName + "> " + message);
                Logger.Log("{0}: {1}", LogType.WorldChat, GetLogName(), message);


            }
            catch (Exception e) { Logger.Log("ERROR: " + e.ToString(), LogType.Error); }
        }

        public void HandleCommand(string cmd, string message)
        {
            try
            {
                if (cmd == "") { Message("No command entered."); return; }
                //if (jailed) { Message("You cannot use any commands while jailed."); return; }
                if (cmd.ToLower() == "ekg") { Message("§cLiudicrous Gibs!"); return; }

                string foundShortcut = Command.all.FindShort(cmd);
                if (foundShortcut != "") cmd = foundShortcut;

                Command command = Command.all.Find(cmd);
                if (command != null)
                {
                    if (info.group.CanExecute(command))
                    {
                        if (cmd != "repeat") lastCMD = cmd + " " + message;
                        //TODO: Add Joker and Mute functionality
                        /*if (this.joker == true || this.muted == true)
                        {
                            if (cmd.ToLower() == "me")
                            {
                                Message("Cannot use /me while muted or jokered.");
                                return;
                            }
                        }*/

                        Logger.Log("CMD: " + name + " used /" + cmd + " " + message, LogType.UserCommand);

                        //this.commThread = new Thread(new ThreadStart(delegate
                        //{
                        try
                        {
                            command.Use(this, message);
                        }
                        catch (Exception e)
                        {
                            Logger.Log("ERROR: " + e.ToString(), LogType.Error);
                            Message("An error occured when using the command!");
                        }
                        //}));
                        //commThread.Start();
                    }
                    else { Message("You are not allowed to use \"" + cmd + "\"!"); }
                }
                else
                {
                    bool retry = true;

                    switch (cmd.ToLower())
                    {    //Check for command switching
                        case "guest": message = message + " " + cmd.ToLower(); cmd = "setrank"; break;
                        case "builder": message = message + " " + cmd.ToLower(); cmd = "setrank"; break;
                        case "advbuilder":
                        case "adv": message = message + " " + cmd.ToLower(); cmd = "setrank"; break;
                        case "operator":
                        case "op": message = message + " " + cmd.ToLower(); cmd = "setrank"; break;
                        case "super":
                        case "superop": message = message + " " + cmd.ToLower(); cmd = "setrank"; break;
                        case "cut": cmd = "copy"; message = "cut"; break;
                        case "admins": message = "superop"; cmd = "viewranks"; break;
                        case "ops": message = "op"; cmd = "viewranks"; break;
                        case "banned": message = cmd; cmd = "viewranks"; break;

                        case "ps": message = "ps " + message; cmd = "map"; break;

                        //Commands from other softwares to seamlessly switch
                        case "bhb":
                        case "hbox": cmd = "cuboid"; message = "hollow"; break;
                        case "blb":
                        case "box": cmd = "cuboid"; break;
                        case "sphere": cmd = "spheroid"; break;
                        case "cmdlist":
                        case "commands":
                        case "cmdhelp": cmd = "help"; break;
                        case "who": cmd = "players"; break;
                        case "levels":
                        case "maps": cmd = "worlds"; break;
                        case "mapsave": cmd = "save"; break;
                        case "mapload": cmd = "load"; break;
                        case "materials": cmd = "blocks"; break;

                        default: retry = false; break;  //Unknown command, then
                    }

                    if (retry) HandleCommand(cmd, message);
                    else Message("Unknown command \"" + cmd + "\"!");
                }
            }
            catch (Exception e) { Logger.Log("ERROR: " + e.ToString(), LogType.Error); Message("Command failed!"); }
        }

        // safety wrapper for session.Send
        public void Send(Packet packet) {
            if (session != null) session.Send(packet);
        }

        public void Send(Packet packet, bool isHighPriority) {
            if (session != null) session.Send(packet, isHighPriority);
        }

        #endregion

        #region Permission Checks

        public bool Can(Permission permissions)
        {
            if (this.info.group.permission >= permissions)
                return true;
            else
                return false;
        }

        public bool CanDraw(int volume) {
            return (info.group.drawLimit > 0) && (volume > info.group.drawLimit);
        }

        #endregion

        #region Entities

        public void HitEntity(Entity hitEntity, InventoryItem hitWith) //The player hitting the entity handles this, doesn't seem right...
        {
            hitEntity.Knockback(this.pos.rotation);

            if (hitEntity is Player)
            {
                Player p = (Player)hitEntity;

                if (p.godMode) return;

                // These aren't imporant, just some fun stuff players can do to eachother.
                // Make some stats out of this, or something.
                if (p.lastMessage.AddMinutes(5) < DateTime.UtcNow && this.lastMessage.AddMinutes(5) < DateTime.UtcNow)
                {
                    this.lastMessage = DateTime.UtcNow;

                    // TODO: Get this in an array for messages
                    switch ((Item)hitWith.Type)
                    {
                        case Item.RawFish:
                            p.Message(this.info.group.color + this.name + Color.Announcement + " slapped you with a " + Color.Blue + "Wet Fish!");
                            break;
                        case Item.Diamond:
                            p.Message(this.info.group.color + this.name + Color.Announcement + " waves their  " + Color.Aqua + "Diamond " + Color.Announcement + "in your face!");
                            break;
                    }
                }

                if (p.info.health > 0) p.Damage(this, hitWith);
            }
            else if (hitEntity is MobEntity)
            {
                MobEntity m = (MobEntity)hitEntity;

                if (m.creature.health > 0 && !m.creature.godMob) m.HurtMob(this, hitWith);
            }
        }

        public void Damage(Player hitByPlayer, InventoryItem hitWithItem) // Player to Player damage
        {
            if (godMode) return;

            if (info.health > 0)
            {
                // TODO: Work out the damage from the item held
                // TODO: Work out amour protection

                info.health--;
                Send(PacketWriter.MakeUpdateHealth(this));
                currentWorld.SendToAllInRange(PacketWriter.MakeAnimation(this, 2), this);
                currentWorld.SendToAllInRange(PacketWriter.MakeEntityStatus(this, 2), this);

                if (info.health < 1)
                {
                    // TODO: Death Messages in text file
                    switch ((Item)hitWithItem.Type)
                    {
                        case Item.Egg:
                            Server.SendToAll(PacketWriter.MakeMessage(Color.Red + this.name + " was egged to death by " + hitByPlayer.name + "!"));
                            break;
                        case Item.RawFish:
                            Server.SendToAll(PacketWriter.MakeMessage(Color.Red + this.name + " was slapped in the face by " + hitByPlayer.name + "!"));
                            break;
                        default:
                            Server.SendToAll(PacketWriter.MakeMessage(Color.Red + this.name + " was bested in battle by " + hitByPlayer.name + "!"));
                            break;
                    }

                    currentWorld.SendToAllInRange(PacketWriter.MakeEntityStatus(this, 3), this);

                    //hitPlayer.Despawn();
                }
            }
        }

        public void Damage(int damage, string damagedBy) // Environment Damage
        {
            if (godMode) return;

            if (info.health > 0)
            {
                info.health--;
                Send(PacketWriter.MakeUpdateHealth(this));
                currentWorld.SendToAllInRange(PacketWriter.MakeAnimation(this, 2), this);
                currentWorld.SendToAllInRange(PacketWriter.MakeEntityStatus(this, 2), this);

                if (info.health < 1)
                {
                    // TODO: Death Messages in text file
                    switch (damagedBy.ToLower())
                    {
                        case "tnt":
                            Server.SendToAll(PacketWriter.MakeMessage(Color.Red + name + " blown to pieces!"));
                            break;
                        case "falling":
                            Server.SendToAll(PacketWriter.MakeMessage(Color.Red + name + " tripped and fell!"));
                            break;
                        case "creeper":
                            Server.SendToAll(PacketWriter.MakeMessage(Color.Red + name + " ...ssssSSSSSS!"));
                            break;
                        default:
                            Server.SendToAll(PacketWriter.MakeMessage(Color.Red + name + " died mysteriously!"));
                            break;

                    }

                    currentWorld.SendToAllInRange(PacketWriter.MakeEntityStatus(this, 3), this);

                    //hitPlayer.Despawn();
                }
            }
        }

        public void UpdateEntity(Entity e, double dx, double dy, double dz, bool rotchanged, bool forceabs, bool velchanged)
        {
            if (!Spawned) return;

            // TODO: Mobs&Objs just don't move properly without this. Another thing to add to the list...
            if (e is MobEntity || e is ObjectEntity)
            {
                Send(PacketWriter.MakeEntityTeleport(e));
                return;
            }

            // TODO: Sends the packet, but doesn't do anything on client!!
            if (velchanged)
            {
                Send(PacketWriter.MakeEntityVelocity(e));
            }

            if (dx == 0 && dy == 0 && dz == 0)
            {
                if (rotchanged)
                {
                    Logger.Log("EntityTurn: " + e.pos.x + " " + e.pos.z, LogType.Debug);
                    Send(PacketWriter.MakeEntityLook(e));
                    //session.Transmit(PacketType.EntityLook, e.id, (sbyte)e.pos.rotation, (sbyte)e.pos.pitch);
                }
            }
            else if (Math.Abs(dx) < 4 && Math.Abs(dy) < 4 && Math.Abs(dz) < 4 && !forceabs)
            {
                Position delta = new Position();

                delta.x = dx * 32;
                delta.y = dy * 32;
                delta.z = dz * 32;
                delta.rotation = e.pos.rotation;
                delta.pitch = e.pos.pitch;

                if (rotchanged)
                {
                    Logger.Log("EntityLookMove: " + e.pos.x + " " + e.pos.z, LogType.Debug);
                    Send(PacketWriter.MakeEntityRelativeMoveLook(e, delta));

                    //session.Transmit(PacketType.EntityLookAndMove, e.id, (sbyte)(dx * 32), (sbyte)(dy * 32), (sbyte)(dz * 32), (sbyte)e.pos.rotation, (sbyte)e.pos.pitch);
                }
                else
                {
                    Logger.Log("EntityMove: " + e.pos.x + " " + e.pos.z, LogType.Debug);
                    Send(PacketWriter.MakeEntityRelativeMove(e, delta));

                    //session.Transmit(PacketType.EntityRelativeMove, e.id, (sbyte)(dx * 32), (sbyte)(dy * 32), (sbyte)(dz * 32));
                }
            }
            else
            {
                Logger.Log("EntityTeleport: " + e.pos.x + " " + e.pos.z, LogType.Debug);
                Send(PacketWriter.MakeEntityTeleport(e));

                //session.Transmit(PacketType.EntityTeleport, e.id, (int)(e.pos.x * 32), (int)(e.pos.y * 32), (int)(e.pos.z * 32), (sbyte)e.pos.rotation, (sbyte)e.pos.pitch);
            }
        }

        public void PickupCollected(PickupEntity pickup, Player player)
        {
            //Send(PacketWriter.MakeCollectItem(pickup, player));

            session.Transmit(PacketType.CollectItem, pickup.id, player.id);
            //session.Transmit(PacketType.WindowItems,(sbyte)window.ID,
        }

        #endregion

        #region Overrides
               
        override public void Despawn()
        {
            if (!Spawned) return;
            Spawned = false;
            base.Despawn();
        }

        //Spawn entities and signs in new chunks.
        override public void Update()
        {
            if (!Spawned) return;

            Chunk newChunk = currentWorld.GetChunk((int)((int)pos.x / 16), (int)((int)pos.z / 16));

            if (newChunk != CurrentChunk)
            {
                if (CurrentChunk != null) CurrentChunk.Entities.Remove(this);
                newChunk.Entities.Add(this);

#if DEBUG
                Send(PacketWriter.MakeMessage("You are in Chunk: " + newChunk.x + " " + newChunk.z));
                Send(PacketWriter.MakeMessage("Visible: " + VisibleChunks.Count));
#endif

                List<Chunk> newVisibleChunks = new List<Chunk>();
                List<BlockPosition> newVisibleSigns = new List<BlockPosition>(); // This holds the list to replace with VisibleSigns
                Dictionary<BlockPosition, SignInfo> newFoundSigns = new Dictionary<BlockPosition, SignInfo>(); // This holds the new data to re-transmit

                int RequiredMinX = (int)((int)pos.x / 16) - (Player.ChunkArea / 2); // ChunkArea
                int RequiredMinZ = (int)((int)pos.z / 16) - (Player.ChunkArea / 2);

                for (int Xchunk = RequiredMinX; Xchunk < (VisibleChunksMinX + Player.ChunkArea); Xchunk++)
                {
                    for (int Zchunk = RequiredMinZ; Zchunk < (VisibleChunksMinZ + Player.ChunkArea); Zchunk++)
                    {
                        newVisibleChunks.Add(currentWorld.GetChunk(Xchunk, Zchunk));

                        foreach (KeyValuePair<BlockPosition, SignInfo> kvp in this.currentWorld.SignList)
                        {
                            if (Xchunk == (int)((int)kvp.Key.x / 16) && Zchunk == (int)((int)kvp.Key.z / 16))
                            {
                                newFoundSigns.Add(kvp.Key, kvp.Value);
                                newVisibleSigns.Add(kvp.Key);
                            }
                        }
                    }
                }

                VisibleChunksMinX = RequiredMinX;
                VisibleChunksMinZ = RequiredMinZ;

                //Check for any newly present signs.
                foreach (KeyValuePair<BlockPosition, SignInfo> kvpds in newFoundSigns)
                {
                    if (!VisibleSigns.Exists(Check => Check.x == kvpds.Key.x) && !VisibleSigns.Exists(Check => Check.y == kvpds.Key.y) && !VisibleSigns.Exists(Check => Check.z == kvpds.Key.z))
                    {
                        Logger.Log("Sending sign to {0}, {1} for {2}, {3}", LogType.Testing, name, kvpds.Value.location, kvpds.Key.x, kvpds.Key.z);

                        Send(PacketWriter.MakeUpdateSign(kvpds.Value));

                        //session.Transmit(PacketType.UpdateSign, int.Parse(blockPos[0]), short.Parse(blockPos[1]), 
                        //  int.Parse(blockPos[2]), kvp.Value.text1, kvp.Value.text2, kvp.Value.text3, kvp.Value.text4);
                    }
                }

                CurrentChunk = newChunk;

                VisibleChunks = newVisibleChunks;
                VisibleSigns = newVisibleSigns;
            }


            List<Entity> newVisibleEntities = new List<Entity>();
            foreach (Chunk c in VisibleChunks)
            {
                foreach (Entity e in c.Entities)
                {
                    newVisibleEntities.Add(e);
                }
            }
            foreach (Entity e in VisibleEntities)
            {
                if (!newVisibleEntities.Contains(e))
                {
                    //Message("Despawning: " + e.id);
                    DespawnEntity(e);
                }
            }
            foreach (Entity e in newVisibleEntities)
            {
                if (!VisibleEntities.Contains(e))
                {
                    //Message("New Spawn: " + e.id);
                    SpawnEntity(e);
                }
            }
            VisibleEntities = newVisibleEntities;

            if (Inventory.slots[36 + SlotSelected].Type != _LastEquipment[0].Type)
            {
                _LastEquipment[0] = Inventory.slots[36 + SlotSelected];
                currentWorld.SendToAllInRange(PacketWriter.MakeEntityEquipment(this, 0, _LastEquipment[0]), this);
            }

            for (short i = 0; i < 4; ++i)
            {
                if (Inventory.slots[5 + i].Type != _LastEquipment[i + 1].Type)
                {
                    _LastEquipment[i + 1] = Inventory.slots[5 + i];
                    //TODO: This is kinda working, but ToNetOrder making things backwards (possibly!)
                    currentWorld.SendToAllInRange(PacketWriter.MakeEntityEquipment(this, i + 1, _LastEquipment[i + 1]), this);
                }
            }
        }

        override public string ToString()
        {
            return "[Entity.Player " + id + ": " + name + "]";
        }

        #endregion

        #region Utils

        // Get log name
        public string GetLogName() {
            return name;
        }

        //This is used to provide cuboid and blockinfo handlers.
        public void SendBlockPosition(BlockPosition bp)
        {
            queryBlockPos = bp;
            waitingCoords = false;
            return;
        }

        public byte GetCompassDirection(byte points = 8)
        {
            if (points == 8)
            {
                if (this.pos.rotation < -111 || this.pos.rotation > 112)
                    return 3;
                else if (this.pos.rotation < -79)
                    return 4;
                else if (this.pos.rotation > 80)
                    return 2;
                else if (this.pos.rotation < -47)
                    return 5;
                else if (this.pos.rotation > 48)
                    return 1;
                else if (this.pos.rotation > 16)
                    return 8;
                else if (this.pos.rotation < -15)
                    return 6;
                else return 7;
            }
            else
            {
                if (this.pos.rotation < -95 || this.pos.rotation > 96)
                    return 3;
                else if (this.pos.rotation < -31)
                    return 5;
                else if (this.pos.rotation > 32)
                    return 1;
                else return 7;
                
            }
            
           
            // This doesn't seem to be quite right and I can't be bothered to fix it.
            /*facingDir = (int)((this.pos.rotation + 240) / 64) + 10;

            if (points == 8) facingDir = (int)(((this.pos.rotation + 240) / 32) - 2);

            switch (facingDir)
            {
                case 3: //North
                case 12:
                    return 1;
                case 2: //NorthWest
                    return 8;
                case 8: //SouthWest
                    return 6;
                case 14:
                case 7: //South
                    return 5;
                case 6: //SouthEast
                    return 4;
                case 5: //East
                case 13:
                    return 3;
                case 4: //NorthEast
                    return 2;
                default: //West
                    return 7;
            }*/



        }


        // Get the coord the player is facing
        //TODO: Rewrite this so it's a bit better!
        // TODO: Replace this with velocity packet as this is what it probably uses...
        public BlockPosition GetFacingCoord()
        {
            BlockPosition playerPos = new BlockPosition((int)pos.x, (byte)pos.y, (int)pos.z);
            BlockPosition facingPos = playerPos;

            int currPos = (int)(pos.rotation / 32);

            switch (currPos)
            {
                case -2: facingPos.x++; break;
                case -1: facingPos.x++; break;
                case 0: facingPos.z++; break;
                case 1: facingPos.x--; break;
                case 2: facingPos.x--; break;
                default: facingPos.z--; break;
            }

            if (currentWorld.GetBlock(facingPos.x, facingPos.y, facingPos.z).type != 0) return playerPos;
            else return facingPos;
        }

        // Validates player name and chat messages
        public static bool CheckForIllegalChars(string message) {
            // are we sure that this is a proper implementation?
            for (int i = 0; i < message.Length; i++) {
                char ch = message[i];
                if (ch < ' ' || ch == '§' || ch == '`' || ch > '}') {
                    return true;
                }
            }
            return false;
        }

        // ensures that player name has the correct length and character set
        public static bool IsValidName(string name) {
            if (name.Length < 2 || name.Length > 16) return false;
            for (int i = 0; i < name.Length; i++) {
                char ch = name[i];
                if (ch < '0' || (ch > '9' && ch < 'A') || (ch > 'Z' && ch < '_') || (ch > '_' && ch < 'a') || ch > 'z') {
                    return false;
                }
            }
            return true;
        }

        internal void ResetIdleTimer() {
            idleTimer = DateTime.UtcNow;
        }

        // Determines if a player can place a certain block
        /*public bool CanPlace(Block block) {
            if (this.info.group.canBuild)
                return true;
            return(!(nonPlaceable.Contains(block)));
        }*/

        #endregion

        #region Helpers

        // TODO: Is this messing with the object/mob despawning?
        private void DespawnEntity(Entity e)
        {
            if (!Spawned || e == this) return;
            //session.Send(PacketWriter.MakeDestroyEntity(e));
            session.Transmit(PacketType.DestroyEntity, e.id);
        }

        private void SpawnEntity(Entity e)
        {
            if (!Spawned || e == this) return;

            if (e is Player)
            {
                Player p = (Player)e;
                session.Send(PacketWriter.MakeEntitySpawn(p));

                /*session.Transmit(PacketType.NamedEntitySpawn, p.id,
                    p.name, (int)(p.pos.x * 32), (int)(p.pos.y * 32), (int)(p.pos.z * 32),
                     (sbyte)p.pos.rotation, (sbyte)p.pos.pitch, (short)Block.Air);*/
            }
            else if (e is PickupEntity)
            {
                PickupEntity p = (PickupEntity)e;
                session.Send(PacketWriter.MakePickupSpawn(p));

                /*session.Transmit(PacketType.PickupSpawn, p.id,
                    p.Item.Type, (sbyte)p.Item.Count, p.Item.Damage,
                    (int)(p.pos.x * 32), (int)(p.pos.y * 32), (int)(p.pos.z * 32),
                     (sbyte)p.pos.rotation, (sbyte)p.pos.pitch, (sbyte)0);*/

            }
            else if (e is MobEntity)
            {
                MobEntity m = (MobEntity)e;

                session.Send(PacketWriter.MakeMobSpawn(m));

                //This raw handle is untested!!
                /*session.Transmit(PacketType.MobSpawn, m.id, (sbyte)m.mob.mobType, 
                    (int)(m.pos.x * 32), (int)(m.pos.y * 32), (int)(m.pos.z * 32),
                     (sbyte)m.pos.rotation, (sbyte)m.pos.pitch, (sbyte)m.mob.metadata);*/

            }
            else if (e is ObjectEntity)
            {
                ObjectEntity o = (ObjectEntity)e;

                session.Send(PacketWriter.MakeObjectSpawn(o));
            }
            else
            {
                Message(Color.Purple + "No handler to spawn " + e);
                return;
            }
            //session.Transmit(PacketType.Entity, e.id);
            session.Send(PacketWriter.MakeEntity(e), true);
        }

        #endregion

        #region Windows

        public void OpenWindow(Window window)
        {
            //session.Transmit(PacketType.OpenWindow, (sbyte)window.ID, (sbyte)window.Type,
                //window.Title, (sbyte)window.slots.Length);

            //This needs to be priority, otherwise SetSlots are ignored by client.
            Send(PacketWriter.MakeOpenWindow(window), true);
        }

        //REALLY picky about packet order, this function should fix.
        public void FillWindow(Window window) 
        {
            if (window.ID > 0)
            {
                for (short i = 0; i < window.slots.Length; i++)
                {
                    if (window.slots[i].Type > 0)
                    {
                        window.SetSlot(i, window.slots[i]);
                    }
                }
            }
        }

        public void WindowSetSlot(Window window, short slot, InventoryItem item)
        {
            //TODO: Client hates holding 'nil' items, must be set to -1
            //This may be fixed - See Window.cs->SetSlot
            Logger.Log("Player.cs->SetSlot: " + window.ID + ":" + window.Title + " S:" + slot + " I:" + item.Type + ":" + item.Count, LogType.Testing);

            if (item.Type < 1) item.Clear();

            Send(PacketWriter.MakeSetSot(window, slot, item), false);
            //session.Transmit(PacketType.WindowSetSlot, (sbyte)window.ID, slot, item);
        }

        public void SetHolding(InventoryItem item)
        {
            //Logger.Log("Player.cs->SetHolding: S:-1 I:" + item.Type + ":" + item.Count, LogType.Testing);
            session.Transmit(PacketType.WindowSetSlot, (sbyte)-1, (short)-1, item);
            WindowHolding = item;
        }

        #endregion
    }
}
